/** @file    NetworkChannel.cpp
 *  @author  Alessandro Polo
 *  @version $Id: NetworkChannel.cpp 2831 2010-08-02 22:15:14Z alex $
 *  @brief
 * File containing methods for the wosh::NetworkChannel class.
 * The header for this class can be found in NetworkChannel.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <core/NetworkChannel.h>
 #include <core/NetworkManager.h>
 #include <core/ObjectFactory.h>
 #include <core/BusManager.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/MethodsCommon.h>
 #include <core/Utilities.h>
 #include <core/DataModels.h>
 #include <core/List.h>
 #include <stdio.h>


using namespace std;
namespace wosh {

 WOSH_REGISTER_INTERFACE(wosh::NetworkChannel, "wosh::Object", 0.66, _static_NetworkChannel)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

NetworkChannel::NetworkChannel( const string& name )
	: Object( NetworkManager::getInstance() ) {
	this->id = Utilities::randomLong();
	this->id = _GET_Abs(this->id);
	if ( name == "" ) Object::setName("NetworkChannel", this->id);
	else Object::setName(name);

	this->listener = NULL;
	this->dumpOutput = NULL;
	this->dumpInput = NULL;

	this->flagDelete = false;
	this->priority = 0;

	this->lastSeen = 0;
	this->messageSentTs = 0;
	this->messageSentCount = 0;
	this->messageReceivedTs = 0;
	this->messageReceivedCount = 0;
	this->messageSentErrorCount = 0;
	this->messageReceivedErrorCount = 0;

	Log.setIndent( 4 );
	Log.setContext( "NetworkChannel" );
	Log.setLevel( LOG_INFO );

openDumpOutputToFile( "net.output.log" );
openDumpInputToFile( "net.input.log" );

	Log(LOG_DEBUG, " Initializing Properties.." );
	Properties.set( _NetworkChannel_KEY_BindAddress,		"",	Permission::RW_RW_R__() );
	Properties.set( _NetworkChannel_KEY_ConnectAddress,		"",	Permission::RW_RW_R__() );
	Properties.set( _NetworkChannel_KEY_Connected,			false, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_Listening,			false, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_Priority,			"100", Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_AutoBind,			true, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_DumpOutputTo,		"", Permission::RW_R__R__() );
	Properties.set( _NetworkChannel_KEY_DumpInputTo,		"", Permission::RW_R__R__() );
	Properties.set( _NetworkChannel_KEY_MessageTxTs,		0, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_MessageRxTs,		0, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_MessageTxCount,		0, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_MessageRxCount,		0, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_MessageTxErrorCount,0, Permission(Permission::Read) );
	Properties.set( _NetworkChannel_KEY_MessageRxErrorCount,0, Permission(Permission::Read) );
	Properties.monitor(_NetworkChannel_KEY_MessageTxCount);
	Properties.monitor(_NetworkChannel_KEY_MessageRxCount);
	Properties.monitor(_NetworkChannel_KEY_MessageTxErrorCount);
	Properties.monitor(_NetworkChannel_KEY_MessageRxErrorCount);
	Properties.monitor(_NetworkChannel_KEY_MessageTxTs);
	Properties.monitor(_NetworkChannel_KEY_MessageRxTs);
	Properties.updateObjectKeys(this);
	Properties.setPropertiesProviderListener(this);

	Log(LOG_DEBUG, " Registering Methods.." );
	MethodPropertyT::createMethods( &Methods, &Properties, &Log );
	MethodChT::createMethods( &Methods, this, &Properties, &Log );

	MethodList* mmListMethods = Methods.create<MethodList>( _METHOD_ListMethods, "List Methods", Permission(Permission::RX) );
	mmListMethods->setDataFieldExtractor( DataFieldExtractor<Method>::createFor< DataAdapterMapT<std::string,Method> >(Methods, "NetworkChannel Methods") );

	MethodRequest* mmConnect = Methods.create<MethodRequest>( _NetworkChannel_METHOD_connect, "connect", Permission::R_XR_XR__() );
	mmConnect->setMethod( this, (MethodRequestPtr)&NetworkChannel::mmDoConnect );

	MethodRequest* mmDisconnect = Methods.create<MethodRequest>( _NetworkChannel_METHOD_disconnect, "disconnect", Permission::R_XR_XR__() );
	mmDisconnect->setMethod( this, (MethodRequestPtr)&NetworkChannel::mmDoDisconnect );

	MethodRequest* mmPing = Methods.create<MethodRequest>( _NetworkChannel_METHOD_ping, "ping", Permission::R_XR_XR__() );
	mmPing->setMethod( this, (MethodRequestPtr)&NetworkChannel::mmDoPing );
 }

NetworkChannel::~NetworkChannel() {
	openDumpOutputToFile( "" );
	openDumpInputToFile( "" );

 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////// 

void NetworkChannel::busMessage( const Message& message, const Bus* ) {
	if ( message.isEmpty() ) return;
	if ( message.getSourceUUID() == this->getEntityID() ) return; // avoid loopback
	sendMessage(&message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////// 

void NetworkChannel::setID( long new_id ) {
	if ( this->id != 0 && this->id != new_id ) {
		Log(LOG_WARNING, ":setID() Changing ID from %ld to %ld", this->id, new_id );
	}
	this->id = new_id;
 }

void NetworkChannel::setSeenNow() {
	MutexLockerWrite mL(&this->statsMux);
	this->lastSeen = Utilities::std_time();
	this->messageReceivedTs = this->lastSeen;
 }

void NetworkChannel::setTransmittedNow( bool tx_succeeded ) {
	MutexLockerWrite mL(&this->statsMux);
	this->messageSentTs = Utilities::std_time();
	++this->messageSentCount;
	if ( !tx_succeeded )
		++this->messageSentErrorCount;
 }
void NetworkChannel::setReceivedNow( bool rx_succeeded ) {
	MutexLockerWrite mL(&this->statsMux);
	this->messageReceivedTs = Utilities::std_time();
	this->lastSeen = this->messageReceivedTs;
	++this->messageReceivedCount;
	if ( !rx_succeeded )
		++this->messageReceivedErrorCount;
 }

WRESULT NetworkChannel::openDumpOutputToFile( const std::string& filename ) {
	if ( this->dumpOutput != NULL ) {
		fflush(this->dumpOutput);
		fclose(this->dumpOutput);
		this->dumpOutput = NULL;
	 }
	if ( filename.empty() ) {
		return WRET_OK;
	 }
	this->dumpOutput = fopen(filename.c_str(), "a");
	if ( this->dumpOutput == NULL )
		return WRET_ERR_INTERNAL;
	return WRET_OK;
 }

WRESULT NetworkChannel::openDumpInputToFile( const std::string& filename ) {
	if ( this->dumpInput != NULL ) {
		fflush(this->dumpInput);
		fclose(this->dumpInput);
		this->dumpInput = NULL;
	 }
	if ( filename.empty() ) {
		return WRET_OK;
	 }
	this->dumpInput = fopen(filename.c_str(), "a");
	if ( this->dumpInput == NULL )
		return WRET_ERR_INTERNAL;
	return WRET_OK;
 }

WRESULT NetworkChannel::setListener( INetworkChannelListener* ch_listener ) {
	 this->listener = ch_listener;
	 return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool NetworkChannel::readingProperty( Property* property_curr, const PropertiesProvider* ) {
	if ( property_curr == NULL ) return true;
	if ( property_curr->getKey() == _NetworkChannel_KEY_MessageTxCount ||
		property_curr->getKey() == _NetworkChannel_KEY_MessageRxCount ||
		property_curr->getKey() == _NetworkChannel_KEY_MessageTxErrorCount || 
		property_curr->getKey() == _NetworkChannel_KEY_MessageRxErrorCount ||
		property_curr->getKey() == _NetworkChannel_KEY_MessageTxTs ||
		property_curr->getKey() == _NetworkChannel_KEY_MessageRxTs )
	 {
		this->statsMux.lockForRead();
		if ( property_curr->getKey() == _NetworkChannel_KEY_MessageTxCount )
			property_curr->setValue( this->messageSentCount );
		else if ( property_curr->getKey() == _NetworkChannel_KEY_MessageRxCount )
			property_curr->setValue( this->messageReceivedCount );
		else if ( property_curr->getKey() == _NetworkChannel_KEY_MessageTxErrorCount )
			property_curr->setValue( this->messageSentErrorCount );
		else if ( property_curr->getKey() == _NetworkChannel_KEY_MessageRxErrorCount )
			property_curr->setValue( this->messageReceivedErrorCount );
		else if ( property_curr->getKey() == _NetworkChannel_KEY_MessageTxTs )
			property_curr->setValue( Variant(this->messageSentTs, Variant::DATETIME) );
		else if ( property_curr->getKey() == _NetworkChannel_KEY_MessageRxTs )
			property_curr->setValue( Variant(this->messageReceivedTs, Variant::DATETIME) );
		this->statsMux.unLock();
	 }
	return true;
 }

bool NetworkChannel::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* ) {
	if ( property_current.getKey() == _KEY_DebugLevel ) {
		return MPC_PropertyParse_LogLevel( value_proposed, this->Log );
	 }
	else if ( property_current.getKey() == _NetworkChannel_KEY_BindAddress ) {
	 }
	else if ( property_current.getKey() == _NetworkChannel_KEY_AutoBind ) {
	 }
	else if ( property_current.getKey() == _NetworkChannel_KEY_Priority ) {
		this->priority = value_proposed.toInteger(0);
		Log(LOG_WARNING, " Changing Priority to '%d'", this->priority );
	 }
	else if ( property_current.getKey() == _NetworkChannel_KEY_DumpOutputTo ) {
		if ( WFAILED(openDumpOutputToFile(value_proposed.toString())) ) {
			property_current.setValue("");
			do_update = false;
			return false;
		 }
		return true;
	 }
	else if ( property_current.getKey() == _NetworkChannel_KEY_DumpInputTo ) {
		if ( WFAILED(openDumpInputToFile(value_proposed.toString())) ) {
			property_current.setValue("");
			do_update = false;
			return false;
		 }
		return true;
	 }
	return true;
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHDOS

Response* NetworkChannel::mmDoConnect( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoConnect() : Calling connect(15000).." );
	WRESULT ret = connect(15000);
	return request->replyResponse(ret);
 }

Response* NetworkChannel::mmDoDisconnect( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoDisconnect() : Calling disconnect(15000).." );
	WRESULT ret = disconnect(15000);
	return request->replyResponse(ret);
 }

Response* NetworkChannel::mmDoPing( const Request* request ) {
	if ( request == NULL ) return NULL;
	Log(LOG_VERBOSE, ":mmDoPing() : Calling ping(10000).." );
	WRESULT ret = ping(10000);
	return request->replyResponse(ret);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHDOS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

DataFieldCollector<NetworkChannel>* NetworkChannel::getDataFields() {
	DataFieldCollector<NetworkChannel>* fields = new DataFieldCollector<NetworkChannel>();
	fields->add<const std::string&, Variant>(&NetworkChannel::getName, "Name", 0);
	fields->add<const char*,Variant>(&NetworkChannel::getClassName, "Type", 1);
	fields->add<const std::string&,Variant>(&NetworkChannel::getRemoteWoshName, "RemoteWoshName", 2);
	fields->add<bool,Variant>(&NetworkChannel::isConnected, "Connected", 3);
	fields->add<std::string,Variant>(&NetworkChannel::getProtocol, "Protocol", 4);
	fields->add<const std::string&,Variant>(&NetworkChannel::getEntityID, "EntityID", 5, false);
	fields->add<const URI&,URI>(&NetworkChannel::getURI, "URI", 6, false);
	fields->add<const Permission&,Permission>(&NetworkChannel::getPermission, "Permission", 7, false);
	return fields;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::NetworkChannel_ListChanged, "wosh::Fact", 1.0, _static_NetworkChannel_ListChanged )

std::string NetworkChannel_ListChanged::getNetworkChannelName() const {
	const ISerializable* item = getItem();
	if ( item != NULL && item->isKindOf<NetworkChannel>() )
		return item->as<NetworkChannel>()->getName();
	const Variant* var = getItemKey();
	if ( var == NULL ) return "";
	return var->toString();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::NetworkChannel_ConnectionChanged, "wosh::Fact", 1.0, _static_NetworkChannel_ConnectionChanged )

NetworkChannel_ConnectionChanged::NetworkChannel_ConnectionChanged( const std::string& network_channel_name, int connection_state )
	: Fact( _NetworkChannel_EVENT_ConnectionChanged ) {
	List* args = List::createFrom<Variant>( network_channel_name, connection_state );
	setData(args);
 }

std::string NetworkChannel_ConnectionChanged::getNetworkChannelName() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return "";
	if ( var->isString() ) return var->asString();
	return var->toString();
 }

int NetworkChannel_ConnectionChanged::getConnectionState() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return 0;
	if ( var->isNumeric() ) return 0;
	return var->toInteger(0);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
